import { Constructor, ECSWorld } from "../ECSWorld";
import { ECSSystem } from "./ECSSystem";
import { ECSSystemSet } from "./ECSSystemSet";

export class ECSSystemSetImpl implements ECSSystemSet {

    private readonly loadingMap = new Map<string, [ECSSystem, boolean]>();
    private readonly loadedMap = new Map<string, ECSSystem>();
    private readonly loadedArr: ECSSystem[] = [];


    private nextPriority = 0;

    private _isRunning: boolean;

    constructor(readonly ecs: ECSWorld) {
        this.ecs = ecs;
    }

    start(): void {
        if (this._isRunning) {
            throw new Error("[ECS] already is running");
        }
        this._isRunning = true;
        this.checkLoading();
    }

    stop(): void {
        if (!this._isRunning) {
            throw new Error("[ECS] not running");
        }

        // 停止所有正在运行的系统
        this.loadedMap.forEach((system, name) => {
            if (system.isRunning === true) {
                system.stop();
            }
        });

        this._isRunning = false;
    }

    update(dt: number): void {
        for (const sys of this.loadedArr) {
            if (sys.enabled) {
                sys.update(dt);
            }
        }
    }

    get<T extends ECSSystem>(constructor: Constructor<T>): T {
        const name = constructor.name;
        const system = this.loadedMap.get(name);

        if (!system) {
            throw new Error(`[ECS] not found system '${name}'`);
        }

        return system as T;
    }

    add(system: ECSSystem, priority?: number): ECSSystemSet {
        const name = system.name;

        if (typeof name !== "string" || name.length === 0) {
            throw new Error(
                `[ECS] system '${system}' not set name by @ecsclass`
            );
        }

        if (this.loadedMap.has(name)) {
            throw new Error(`[ECS] system '${name}' already exists`);
        }

        if (this.loadingMap.has(name)) {
            throw new Error(`[ECS] system '${name}' already in loading`);
        }

        system.ecs = this.ecs;

        if (typeof priority === 'number') {
            system.priority = priority;
            if (priority > this.nextPriority) {
                this.nextPriority = priority + 1;
            }
        } else {
            system.priority = this.nextPriority;
            this.nextPriority++;
        }

        this.loadingMap.set(name, [system, false]);

        system.load().then(() => {
            this.loadingMap.set(name, [system, true]);
            this.checkLoading();
        }).catch((e) => {
            throw e;
        });

        return this;
    }

    delete(sys: ECSSystem): ECSSystemSet {
        const name = sys.name;
        if (this.loadedMap.has(name)) {
            // 从载入完成的列表中删除指定 system
            for (let i = 0, l = this.loadedArr.length; i < l; i++) {
                if (this.loadedArr[i].name === name) {
                    this.loadedArr.splice(i, 1);
                    break;
                }
            }
            // js.array.fastRemove(this.loadedList, sys); 
            this.loadedMap.delete(name);
        } else if (this.loadingMap.has(name)) {
            // 从正在载入的列表中删除指定 system
            this.loadingMap.delete(name);
        } else {
            throw new Error(`[ECS] not found system '${name}`);
        }

        // 检查是否需要停止系统
        if (sys.isRunning)
            sys.stop();

        // 卸载系统
        sys.unload();
        sys.ecs = undefined;

        // 检查载入中的系统
        this.checkLoading();

        return this;
    }

    clear(): ECSSystemSet {
        this.loadedMap.forEach((system) => this.delete(system));
        this.loadingMap.forEach(([system]) => this.delete(system));
        return this;
    }

    sort(): ECSSystemSet {
        this.loadedArr.sort((a: ECSSystem, b: ECSSystem): number => {
            if (a.priority > b.priority) {
                return 1;
            } else if (a.priority < b.priority) {
                return -1;
            } else {
                return 0;
            }
        });
        return this;
    }


    private checkLoading(): void {
        // 已经载入完成的系统移动到 loaded 列表
        // 未载入完成的保持在 loading 列表中
        const removes: string[] = [];
        this.loadingMap.forEach(([system, loaded], name) => {
            if (loaded) {
                this.loadedArr.push(system);
                this.loadedMap.set(name, system);
                removes.push(name);
            }
        });
        // 从正在载入列表清理已经完成载入的系统
        removes.forEach((name) => this.loadingMap.delete(name));

        // 重新排序
        this.sort();

        if (this.loadingMap.size > 0) {
            // 如果还有系统未加载完成，则继续等待
            return;
        }

        if (!this._isRunning) return;

        // 启动所有还未启动的系统
        for (const system of this.loadedArr) {
            if (system.isRunning !== true) {
                system.start();
            }
        }
    }
}